/*
 *
 * File Name: tests.c
 * Author: liaoxuecheng
 * mail: liaoxuecheng@hotmail.com
 * Created Time: 2024年12月27日 星期五 14时44分36秒
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include "tests.h"

#define THIS_FILE "tests.c"

#ifndef PJ_LOG
#define TEST_PRINTF(title,fmt,arg...) printf("%s:%d %s, "fmt, __func__, __LINE__, title, ##arg)
#define PJ_LOG(arg, fmt) { printf("level = %d, ",arg);TEST_PRINTF fmt; }
#endif

#ifdef SERVER_OBJ_INTERFACE
#undef SERVER_OBJ_INTERFACE
#define SERVER_OBJ_INTERFACE  SERVER_OBJ_INTERFACE_TEST
#endif

#if TEST_SERVER
static void path_unregistered_func(DBusConnection *connection, void *user_data);
static DBusHandlerResult path_message_test_func(DBusConnection *connection, DBusMessage *message, void *user_data);

struct {
    int cnt_method_call;
    int handled_method_call;
    int err_method_call;
    int cnt_signal;
    int handled_signal;
    int err_signal;
}test_result;

static DBusObjectPathVTable test_vtable = {
    path_unregistered_func,
    path_message_test_func,
    NULL,
};

int test_vtable_register_object_path(DBusConnection *connection)
{
    void *d = NULL;
    if(!dbus_connection_register_object_path (connection, SERVER_OBJ_PATH_TEST, &test_vtable, (void*) 0xdeadbeef))
    {
        PJ_LOG(1, (THIS_FILE, "No memory"));
        exit(1);
    }

    if (!dbus_connection_get_object_path_data (connection, SERVER_OBJ_PATH_TEST, &d))
    {
        PJ_LOG(1, (THIS_FILE, "No memory"));
        exit(1);
    }
    if (d != (void*)0xdeadbeef)
    {
        PJ_LOG(1, (THIS_FILE, "dbus_connection_get_object_path_data() doesn't seem to work right\n"));
        exit(1);
    }
    DBusError error;
    dbus_bus_add_match(connection,"type='signal',path='" SERVER_OBJ_PATH_TEST "'",&error);
    if(dbus_error_is_set(&error))
        dbus_error_free(&error);
    return 0;
}

static void path_unregistered_func(DBusConnection *connection, void *user_data)
{
    /* connection was finalized */
}

static DBusHandlerResult handle_variant_is(DBusConnection *connection, DBusMessage *message)
{
    int len = 0;
    char *p = NULL;
    test_result.cnt_method_call += 1;
    at_variant *variant = get_variant_from_message(message);
    if(variant == NULL)
    {
        test_result.err_method_call += 1;
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

    at_variant_get(variant, "is", &len, &p);
    if(!len || !p)
    {
        test_result.err_method_call += 1;
        at_dbus_send_message_response_error(connection, message, DBUS_ERROR_FAILED, "Invalid data");
        at_variant_free(variant);
        return DBUS_HANDLER_RESULT_HANDLED;
    }

    at_variant *response = at_variant_new("is", len, p);
    if(response == NULL)
    {
        test_result.err_method_call += 1;
        at_dbus_send_message_response_error(connection, message, DBUS_ERROR_FAILED, "Not enough memory");
        at_variant_free(variant);
        return DBUS_HANDLER_RESULT_HANDLED;
    }
    test_result.handled_method_call += 1;
    at_dbus_send_message_response_success(connection, message, response);
    at_variant_free(variant);
    at_variant_free(response);
    return DBUS_HANDLER_RESULT_HANDLED;
}

static DBusHandlerResult handle_variant_au(DBusConnection *connection, DBusMessage *message)
{
    int len = 0;
    char *p = NULL;
    test_result.cnt_method_call += 1;
    at_variant *variant = get_variant_from_message(message);
    if(variant == NULL)
    {
        test_result.err_method_call += 1;
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

    at_variant_get(variant, "au", &p, &len);
    if(!len || !p)
    {
        test_result.err_method_call += 1;
        at_dbus_send_message_response_error(connection, message, DBUS_ERROR_FAILED, "Invalid data");
        at_variant_free(variant);
        return DBUS_HANDLER_RESULT_HANDLED;
    }

    at_variant *response = at_variant_new("au", p, len);
    if(response == NULL)
    {
        test_result.err_method_call += 1;
        at_dbus_send_message_response_error(connection, message, DBUS_ERROR_FAILED, "Not enough memory");
        at_variant_free(variant);
        return DBUS_HANDLER_RESULT_HANDLED;
    }
    test_result.handled_method_call += 1;
    at_dbus_send_message_response_success(connection, message, response);
    at_variant_free(variant);
    at_variant_free(response);
    return DBUS_HANDLER_RESULT_HANDLED;
}

static DBusHandlerResult handle_variant_au_no_reply(DBusConnection *connection, DBusMessage *message)
{
    int len = 0;
    char *p = NULL;
    test_result.cnt_method_call += 1;
    at_variant *variant = get_variant_from_message(message);
    if(variant == NULL)
    {
        test_result.err_method_call += 1;
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

    at_variant_get(variant, "au", &p, &len);
    if(!len || !p)
    {
        test_result.err_method_call += 1;
        at_dbus_send_message_response_error(connection, message, DBUS_ERROR_FAILED, "Invalid data");
        at_variant_free(variant);
        return DBUS_HANDLER_RESULT_HANDLED;
    }
    test_result.handled_method_call += 1;
    at_variant_free(variant);
    return DBUS_HANDLER_RESULT_HANDLED;
}

static DBusHandlerResult handle_variant_au_async(DBusConnection *connection, DBusMessage *message)
{
    int len = 0;
    char *p = NULL;
    test_result.cnt_method_call += 1;
    at_variant *variant = get_variant_from_message(message);
    if(variant == NULL)
    {
        test_result.err_method_call += 1;
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

    at_variant_get(variant, "au", &p, &len);
    if(!len || !p)
    {
        test_result.err_method_call += 1;
        at_dbus_send_message_response_error(connection, message, DBUS_ERROR_FAILED, "Invalid data");
        at_variant_free(variant);
        return DBUS_HANDLER_RESULT_HANDLED;
    }

    at_variant *response = at_variant_new("au", p, len);
    if(response == NULL)
    {
        test_result.err_method_call += 1;
        at_dbus_send_message_response_error(connection, message, DBUS_ERROR_FAILED, "Not enough memory");
        at_variant_free(variant);
        return DBUS_HANDLER_RESULT_HANDLED;
    }
    test_result.handled_method_call += 1;
    at_dbus_send_message_response_success(connection, message, response);
    at_variant_free(variant);
    at_variant_free(response);
    return DBUS_HANDLER_RESULT_HANDLED;
}



static DBusHandlerResult handle_variant_signal(DBusConnection *connection, DBusMessage *message)
{
    int ia = 0, ib = 0, ic = 0, id = 0;
    at_variant *variant = get_variant_from_message(message);
    test_result.cnt_signal += 1;
    if(variant == NULL)
    {
        test_result.err_signal += 1;
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

    at_variant_get(variant, "iiii", &ia, &ib, &ic, &id);

    test_result.handled_signal += 1;
    at_variant_free(variant);
    return DBUS_HANDLER_RESULT_HANDLED;
}

static DBusHandlerResult handle_variant_total(DBusConnection *connection, DBusMessage *message)
{
    int ia = 0, ib = 0, ic = 0, id = 0;
    at_variant *variant = get_variant_from_message(message);
    if(variant == NULL)
    {
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

    at_variant_get(variant, "iiii", &ia, &ib, &ic, &id);
    PJ_LOG(1, (THIS_FILE, "variant_total:%d,%d,%d,%d\n", ia, ib, ic, id));
    PJ_LOG(1, (THIS_FILE, "cnt_method_call:%d, handled_method_call:%d, err_method_call:%d\n", test_result.cnt_method_call, test_result.handled_method_call, test_result.err_method_call));
    PJ_LOG(1, (THIS_FILE, "cnt_signal:%d, handled_signal:%d, err_signal:%d\n", test_result.cnt_signal, test_result.handled_signal, test_result.err_signal));
    at_variant_free(variant);
    return DBUS_HANDLER_RESULT_HANDLED;
}

static DBusHandlerResult path_message_test_func(DBusConnection *connection, DBusMessage *message, void *user_data)
{
    if(dbus_message_is_method_call(message, SERVER_OBJ_INTERFACE, "variant_is"))
    {
        return handle_variant_is(connection, message);
    }
    else if(dbus_message_is_method_call(message, SERVER_OBJ_INTERFACE, "variant_au"))
    {
        return handle_variant_au(connection, message);
    }
    else if(dbus_message_is_method_call(message, SERVER_OBJ_INTERFACE, "variant_no_reply_au"))
    {
        return handle_variant_au_no_reply(connection, message);
    }
    else if(dbus_message_is_method_call(message, SERVER_OBJ_INTERFACE, "variant_au_async"))
    {
        return handle_variant_au_async(connection, message);
    }
    else if(dbus_message_is_signal(message, SERVER_OBJ_INTERFACE, "variant_signal"))
    {
        return handle_variant_signal(connection, message);
    }
    else if(dbus_message_is_signal(message, SERVER_OBJ_INTERFACE, "variant_total"))
    {
        return handle_variant_total(connection, message);
    }
}
#endif




#if TEST_CLIENT

static inline int generate_pseudo_int()
{
    return rand();
}

static int generate_pseudo_string(char *s, int max_len)
{
    union
    {
        int i;
        unsigned char c[4];
    }tmp;
    max_len = ((unsigned)rand()) % (max_len - 1) + 1;
    while(max_len > 0)
    {
        tmp.i = rand();
        snprintf(s, max_len, "%02x%02x%02x%02x", tmp.c[0], tmp.c[1], tmp.c[2], tmp.c[3]);
        s += 8;
        max_len -= 8;
    }
    return 0;
}

static int generate_pseudo_binrary(unsigned char *a, int *rmax_len)
{
    int tmp = 0;
    *rmax_len = ((unsigned)rand()) % (*rmax_len - 1) + 1;
    int max_len = *rmax_len;
    int m = max_len / 4;
    int d = max_len % 4;
    while(m > 0)
    {
        tmp = rand();
        memcpy(a, &tmp, 4);
        a += 4;
        m -= 1;
    }
    if(d)
    {
        tmp = rand();
        memcpy(a, &tmp, d);
    }
    return 0;
}

int test_method_call_sync_is(at_proxy *test_proxy)
{
    DBusConnection *conn;
    DBusError error;
    at_variant *variant = NULL, *reply = NULL;
    char msg[1024] = {0};
    int len = 0;
    char *ret_msg = NULL;
    int ret_len = 0;

    generate_pseudo_string(msg, sizeof(msg));
    len = generate_pseudo_int();
    dbus_error_init (&error);
    conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
    if(conn == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "Failed to open connection: %s\n", error.message));
        dbus_error_free(&error);
        return -1;
    }

    variant = at_variant_new("is", len, msg);
    if(variant == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "No memory\n"));
        return -1;
    }

    reply = at_dbus_call_sync(conn, test_proxy, "variant_is", variant, &error);
    if(reply == NULL && dbus_error_is_set(&error))
    {
        PJ_LOG(1, (THIS_FILE, "method call error: %s\n", error.message));
        sleep(10);
        dbus_error_free(&error);
        goto ERR;
    }

    at_variant_get(reply, "is", &ret_len, &ret_msg);
    if(ret_len != len || strcmp(msg, ret_msg))
    {
        PJ_LOG(1, (THIS_FILE, "data error:[%s:%s] [%d:%d]\n", msg, ret_msg, len, ret_len));
        goto ERR;
    }

    at_variant_free(reply);
    at_variant_free(variant);
    dbus_connection_unref(conn);
    return 0;
ERR:
    at_variant_free(reply);
    at_variant_free(variant);
    dbus_connection_unref(conn);
    return -1;
}

int test_method_call_sync_au(at_proxy *test_proxy)
{
    DBusConnection *conn;
    DBusError error;
    at_variant *variant = NULL, *reply = NULL;
    unsigned char msg[256] = {0};
    int len = sizeof(msg) - 1;
    unsigned char *ret_msg = NULL;
    int ret_len = 0;

    generate_pseudo_binrary(msg, &len);
    dbus_error_init (&error);
    conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
    if(conn == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "Failed to open connection: %s\n", error.message));
        dbus_error_free(&error);
        return -1;
    }

    variant = at_variant_new("au", msg, len);
    if(variant == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "No memory\n"));
        return -1;
    }

    reply = at_dbus_call_sync(conn, test_proxy, "variant_au", variant, &error);
    if(reply == NULL && dbus_error_is_set(&error))
    {
        PJ_LOG(1, (THIS_FILE, "method call error: %s\n", error.message));
        sleep(10);
        dbus_error_free(&error);
        goto ERR;
    }

    at_variant_get(reply, "au", &ret_msg, &ret_len);
    if(ret_len != len || memcmp(msg, ret_msg, len))
    {
        PJ_LOG(1, (THIS_FILE, "data error:[%s:%s] [%d:%d]\n", msg, ret_msg, len, ret_len));
        goto ERR;
    }

    at_variant_free(reply);
    at_variant_free(variant);
    dbus_connection_unref(conn);
    return 0;
ERR:
    at_variant_free(reply);
    at_variant_free(variant);
    dbus_connection_unref(conn);
    return -1;
}

int test_method_call_au(at_proxy *test_proxy)
{
    DBusConnection *conn;
    DBusError error;
    at_variant *variant = NULL, *reply = NULL;
    unsigned char msg[256] = {0};
    int len = sizeof(msg) - 1;
    unsigned char *ret_msg = NULL;
    int ret_len = 0;

    generate_pseudo_binrary(msg, &len);
    dbus_error_init (&error);
    conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
    if(conn == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "Failed to open connection: %s\n", error.message));
        dbus_error_free(&error);
        return -1;
    }

    variant = at_variant_new("au", msg, len);
    if(variant == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "No memory\n"));
        return -1;
    }
    at_dbus_call(conn, test_proxy, "variant_no_reply_au", variant);

    at_variant_free(variant);
    dbus_connection_unref(conn);
    return 0;
}

static void call_async_callback(DBusPendingCall *pending, void *user_data)
{
    int len[2] = {0};
    unsigned char *p[2] = {NULL};
    at_variant *src = (at_variant *)user_data;
    at_variant *variant = NULL;

    DBusMessage *message = dbus_pending_call_steal_reply(pending);

    if(message == NULL)
    {
        goto END;
    }

    variant = get_variant_from_message(message);
    if(variant == NULL)
    {
        goto END;
    }
    at_variant_get(src, "au", &p[0], &len[0]);
    at_variant_get(variant, "au", &p[1], &len[1]);

    if(len[0] != len[1] || memcmp(p[0], p[1], len[0]))
    {
        PJ_LOG(1, (THIS_FILE, "data error:[%s:%s] [%d:%d]\n", p[0], p[1], len[0], len[1]));
        goto END;
    }
    static int cnt = 0;
    cnt++;
    if(cnt % 10000 == 9999)
        PJ_LOG(1, (THIS_FILE, "cnt = %d\n", cnt));
END:
    at_variant_free(variant);
#if USE_ASYNC_DATA_FREE
#else
    at_variant_free(src);
#endif
    dbus_message_unref(message);
    dbus_pending_call_unref(pending);
    return;
}

static void variant_data_free(void *arg)
{
    at_variant_free((at_variant *)arg);
}

int test_method_call_async_au(at_proxy *test_proxy)
{
    DBusConnection *conn;
    DBusError error;
    at_variant *variant = NULL, *reply = NULL;
    unsigned char msg[256] = {0};
    int len = sizeof(msg) - 1;
    unsigned char *ret_msg = NULL;
    int ret_len = 0;

    generate_pseudo_binrary(msg, &len);
    dbus_error_init (&error);
    conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
    if(conn == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "Failed to open connection: %s\n", error.message));
        dbus_error_free(&error);
        return -1;
    }

    variant = at_variant_new("au", msg, len);
    if(variant == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "No memory\n"));
        return -1;
    }

    /* call_async_callback used variant and then at_variant_free will free variant */
#if USE_ASYNC_DATA_FREE
    at_dbus_call_async2(conn, test_proxy, "variant_au_async", variant, call_async_callback, variant, variant_data_free);
#else
    at_dbus_call_async(conn, test_proxy, "variant_au_async", variant, call_async_callback, variant);
#endif

    //at_variant_free(variant);
    dbus_connection_unref(conn);
    return 0;
}

int test_signal_iiii(at_proxy *test_proxy)
{
    DBusConnection *conn;
    DBusError error;
    at_variant *variant;

    dbus_error_init (&error);
    conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
    if(conn == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "Failed to open connection: %s\n", error.message));
        dbus_error_free (&error);
        return 1;
    }

    variant = at_variant_new("iiii", generate_pseudo_int(), generate_pseudo_int(), generate_pseudo_int(), generate_pseudo_int());
    at_dbus_send_signal(conn, test_proxy->object_path, test_proxy->interface, "variant_signal", variant);
    at_variant_free(variant);
    dbus_connection_unref(conn);
    return 0;
}

int report_signal_test_data(at_proxy *test_proxy, int a, int b, int c, int d)
{
    DBusConnection *conn;
    DBusError error;
    at_variant *variant;

    dbus_error_init (&error);
    conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
    if(conn == NULL)
    {
        PJ_LOG(1, (THIS_FILE, "Failed to open connection: %s\n", error.message));
        dbus_error_free (&error);
        return 1;
    }

    variant = at_variant_new("iiii", a, b, c, d);
    at_dbus_send_signal(conn, test_proxy->object_path, test_proxy->interface, "variant_total", variant);
    at_variant_free(variant);
    dbus_connection_unref(conn);
    return 0;
}

static int __test_all()
{
    srand(time(NULL));
    at_proxy *test_proxy = at_proxy_new(SERVER_OBJ_NAME, SERVER_OBJ_PATH_TEST, SERVER_OBJ_INTERFACE);
    int i, test_cnt;

#define COLOR(s) "\e[33;1m" s "\e[0m"
#define FUNC_AND_NAME(f, n) f, COLOR(#f), n, n
    struct
    {
        int (*callback)(at_proxy *);
        const char *name;
        int test_cnt;
        int test_down;
    }test_data[] =
    {
#if 0
#else
        {FUNC_AND_NAME(test_method_call_sync_is, 600001)},
        {FUNC_AND_NAME(test_method_call_sync_au, 800002)},
        {FUNC_AND_NAME(test_method_call_au, 700002)},
        {FUNC_AND_NAME(test_method_call_async_au, 500002)},
        {FUNC_AND_NAME(test_signal_iiii, 900005)},
#endif
    };

    while(1)
    {
        i = (unsigned int)rand() % ARR_SIZE(test_data);
        if(test_data[i].test_cnt)
        {
            test_data[i].test_cnt--;
            test_data[i].callback(test_proxy);
        }
        test_cnt = 0;
        for(i = 0; i < ARR_SIZE(test_data); i++)
            test_cnt += test_data[i].test_cnt;
        if(test_cnt == 0)
            break;
    }

    report_signal_test_data(test_proxy, 1000, 0, 2000, 0);
    at_proxy_free(test_proxy);

    for(i = 0; i < ARR_SIZE(test_data); i++)
    {
        PJ_LOG(1, (THIS_FILE, "%s, test_cnt:%d OK\n", test_data[i].name, test_data[i].test_down));
    }
    return 0;
}

void *__test_func(void *arg)
{
    pthread_detach(pthread_self());
    time_t t = time(NULL);
    __test_all();
    time_t t2 = time(NULL);
    PJ_LOG(1, (THIS_FILE, "Test OK thread_id:%lu, time:%lu\n", pthread_self(), t2 - t));
    return NULL;
}

int test_method_and_signal_random()
{
    pthread_t thread;
    pthread_create(&thread, NULL, __test_func, NULL);
    pthread_create(&thread, NULL, __test_func, NULL);
    pthread_create(&thread, NULL, __test_func, NULL);
    pthread_create(&thread, NULL, __test_func, NULL);
}

#endif
